using YMJake.Flowable.ExternalWorker.Client.Core.Extensions;
using YMJake.Flowable.ExternalWorker.Client.Core.Rest;
using YMJake.Flowable.ExternalWorker.Client.Core.Variables;

namespace YMJake.Flowable.ExternalWorker.Client.Core.Client;

/// <summary>
/// A result that can be returned by a worker handler.
/// A result can be created by using WorkerResultBuilder from the pipeline context.
/// Similar to Java's sealed interface WorkerResult.
/// </summary>
public abstract class WorkerResult
{
    // Private constructor to prevent external inheritance (simulates sealed interface)
    private protected WorkerResult() { }

    /// <summary>
    /// Execute the result operation
    /// </summary>
    public abstract Task ExecuteAsync(IAcquiredExternalWorkerJob job, IFlowableExternalWorkerRestClient restClient);

    /// <summary>
    /// A failure result.
    /// It allows for configuring the failure details.
    /// Can be created via WorkerResultBuilder.Failure()
    /// </summary>
    public sealed class Failure : WorkerResult
    {
        public string Message { get; private set; } = "Unknown error";
        public string Details { get; private set; } = "";
        public int Retries { get; private set; }

        internal Failure() { }

        /// <summary>
        /// Provide fail information based on the exception.
        /// The exception message will be used as the failure message
        /// and the exception details will be used as the details.
        /// </summary>
        /// <param name="exception">the exception that should be used to provide the message and details</param>
        /// <returns>this Failure for method chaining</returns>
        public Failure Error(Exception exception)
        {
            if (exception == null) throw new ArgumentNullException(nameof(exception));
            
            Message = exception.Message;
            Details = exception.ToString();
            return this;
        }

        /// <summary>
        /// The failure message that should be passed to the Flowable application.
        /// </summary>
        /// <param name="message">the failure message</param>
        /// <returns>this Failure for method chaining</returns>
        public Failure WithMessage(string message)
        {
            Message = message ?? throw new ArgumentNullException(nameof(message));
            return this;
        }

        /// <summary>
        /// The failure details that should be passed to the Flowable application.
        /// </summary>
        /// <param name="details">the failure details</param>
        /// <returns>this Failure for method chaining</returns>
        public Failure WithDetails(string details)
        {
            Details = details;
            return this;
        }

        /// <summary>
        /// The number of retries that should be set when failing the job.
        /// </summary>
        /// <param name="retries">the number of retries</param>
        /// <returns>this Failure for method chaining</returns>
        public Failure WithRetries(int retries)
        {
            Retries = Math.Max(0, retries);
            return this;
        }

        /// <summary>
        /// The timeout for the next retry.
        /// </summary>
        /// <param name="retryTimeout">the retry timeout</param>
        /// <returns>this Failure for method chaining</returns>
        public Failure RetryTimeout(TimeSpan retryTimeout)
        {
            // For now, we don't support retry timeout in the REST API
            // This is here for API compatibility
            return this;
        }

        public override async Task ExecuteAsync(IAcquiredExternalWorkerJob job, IFlowableExternalWorkerRestClient restClient)
        {
            await restClient.FailJob(job.Id, Message, Details, Retries);
        }
    }

    /// <summary>
    /// A successful result.
    /// It allows for configuring the completion details.
    /// Can be created via WorkerResultBuilder.Success()
    /// </summary>
    public sealed class Success : WorkerResult
    {
        private readonly VariableExtensions _variables = new();

        internal Success() { }

        /// <summary>
        /// Add a strongly-typed variable to the result
        /// </summary>
        public Success Variable(string name, VariableBase variable)
        {
            _variables.Set(name, variable);
            return this;
        }

        /// <summary>
        /// Provide a string variable (convenience method)
        /// </summary>
        public Success Variable(string name, string value)
        {
            _variables.Set(name, value);
            return this;
        }

        /// <summary>
        /// Provide an integer variable (convenience method)
        /// </summary>
        public Success Variable(string name, int value)
        {
            _variables.Set(name, value);
            return this;
        }

        /// <summary>
        /// Provide a long variable (convenience method)
        /// </summary>
        public Success Variable(string name, long value)
        {
            _variables.Set(name, value);
            return this;
        }

        /// <summary>
        /// Provide a double variable (convenience method)
        /// </summary>
        public Success Variable(string name, double value)
        {
            _variables.Set(name, value);
            return this;
        }

        /// <summary>
        /// Provide a boolean variable (convenience method)
        /// </summary>
        public Success Variable(string name, bool value)
        {
            _variables.Set(name, value);
            return this;
        }

        /// <summary>
        /// Provide a DateTime variable (convenience method)
        /// </summary>
        public Success Variable(string name, DateTime value)
        {
            _variables.Set(name, value);
            return this;
        }

        /// <summary>
        /// Provide a DateOnly variable (convenience method)
        /// </summary>
        public Success Variable(string name, DateOnly value)
        {
            _variables.Set(name, value);
            return this;
        }

        /// <summary>
        /// Convert and add a JSON variable
        /// </summary>
        public Success ConvertAndAddJsonVariable<T>(string name, T value)
        {
            _variables.SetJson(name, value);
            return this;
        }

        public override async Task ExecuteAsync(IAcquiredExternalWorkerJob job, IFlowableExternalWorkerRestClient restClient)
        {
            var engineVariables = _variables.Any() ? _variables.ToEngineRestVariables() : null;
            await restClient.CompleteJob(job.Id, engineVariables);
        }
    }

    /// <summary>
    /// A BPMN error result.
    /// This will trigger a BPMN error in the process.
    /// Can be created via WorkerResultBuilder.BpmnError()
    /// </summary>
    public sealed class BpmnError : WorkerResult
    {
        private readonly VariableExtensions _variables = new();
        
        public string? ErrorCode { get; private set; }

        internal BpmnError() { }
        
        internal BpmnError(string errorCode)
        {
            ErrorCode = errorCode;
        }

        /// <summary>
        /// Set the error code for the BPMN error
        /// </summary>
        /// <param name="errorCode">the error code</param>
        /// <returns>this BpmnError for method chaining</returns>
        public BpmnError WithErrorCode(string errorCode)
        {
            ErrorCode = errorCode;
            return this;
        }

        /// <summary>
        /// Add a strongly-typed variable to the BPMN error
        /// </summary>
        public BpmnError Variable(string name, VariableBase variable)
        {
            _variables.Set(name, variable);
            return this;
        }

        /// <summary>
        /// Add a string variable (convenience method)
        /// </summary>
        public BpmnError Variable(string name, string value)
        {
            _variables.Set(name, value);
            return this;
        }

        /// <summary>
        /// Add an integer variable (convenience method)
        /// </summary>
        public BpmnError Variable(string name, int value)
        {
            _variables.Set(name, value);
            return this;
        }

        public override async Task ExecuteAsync(IAcquiredExternalWorkerJob job, IFlowableExternalWorkerRestClient restClient)
        {
            var engineVariables = _variables.Any() ? _variables.ToEngineRestVariables() : null;
            await restClient.JobWithBpmnError(job.Id, engineVariables, ErrorCode);
        }
    }

    /// <summary>
    /// A CMMN termination result.
    /// This will terminate the CMMN case.
    /// Can be created via WorkerResultBuilder.CmmnTerminate()
    /// </summary>
    public sealed class CmmnTermination : WorkerResult
    {
        private readonly VariableExtensions _variables = new();

        internal CmmnTermination() { }

        /// <summary>
        /// Add a strongly-typed variable to the CMMN termination
        /// </summary>
        public CmmnTermination Variable(string name, VariableBase variable)
        {
            _variables.Set(name, variable);
            return this;
        }

        /// <summary>
        /// Add a string variable (convenience method)
        /// </summary>
        public CmmnTermination Variable(string name, string value)
        {
            _variables.Set(name, value);
            return this;
        }

        public override async Task ExecuteAsync(IAcquiredExternalWorkerJob job, IFlowableExternalWorkerRestClient restClient)
        {
            var engineVariables = _variables.Any() ? _variables.ToEngineRestVariables() : null;
            await restClient.JobWithCmmnTerminate(job.Id, engineVariables);
        }
    }
}
